package p2p

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"osiris/core/conversion"
	"osiris/dto"
	"osiris/logger"
	"osiris/ocrypto/ed25519"
	"sync"

	"github.com/libp2p/go-libp2p/core/network"
	peer "github.com/libp2p/go-libp2p/core/peer"
	"github.com/libp2p/go-libp2p/core/protocol"
)

// BootstrapProtocol Bootstrap节点发现协议：拿到节点列表后会自动跟未知的节点握手
type BootstrapProtocol struct{}

func (protocol BootstrapProtocol) protocolName() protocol.ID {
	return "/p2p/bootstrap"
}

func (protocol BootstrapProtocol) openStream(peerID peer.ID, jsonStr string, wg *sync.WaitGroup) {
	s, err := ha.NewStream(context.Background(), peerID, protocol.protocolName())
	if err != nil {
		if wg != nil {
			wg.Done()
		}
		logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Open stream %s] Ha.NewStream", protocol.protocolName()): err})
		return
	} else {
		logger.Printf("Sucessfully open stream %s with remote peer %s\n", protocol.protocolName(), peerID.String())
		logger.Info(map[string]interface{}{fmt.Sprintf("[p2p] [Open stream %s] Ha.NewStream", protocol.protocolName()): "success"})
	}

	// Create a buffered stream so that read and writes are non blocking.
	rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))

	// 不可能同时读和写，就开一个协程
	go func() {
		errorOccured := true
		defer func() {
			if wg != nil {
				wg.Done()
			}
			if errorOccured {
				s.Close()
			}
		}()

		//等待ECHO,或网络流被对方节点关闭
		str, err2 := rw.ReadString('\n')
		if err2 != nil {
			//TODO confirm: what exactly will happen if the remote side close the stream?
			//if err == io.EOF {}
			logger.Printf("Stream %s is closed by the remote side\n", protocol.protocolName())
			logger.Info(map[string]interface{}{fmt.Sprintf("[p2p] [Open Stream %s]", protocol.protocolName()): "Stream closed by the remote side"})
			return
		}

		//读完ECHO关流，处理Dto
		s.Close()
		errorOccured = false
		logger.Printf("Stream %s is closed by the local side\n", protocol.protocolName())
		logger.Info(map[string]interface{}{fmt.Sprintf("[p2p] [Open Stream %s]", protocol.protocolName()): "Stream closed by the local side"})

		var multiPeerDto dto.MultiPeerDto
		if err3 := json.Unmarshal([]byte(str), &multiPeerDto); err3 != nil {
			logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Open Stream %s] MultiPeerDto unmarshal", protocol.protocolName()): err3})
			return
		}

		unstoredPeerNum := 0
		//尝试和Bootstrap返回的所有未知节点挨个握手
		for peerIDStr, addrStrs := range multiPeerDto.AddrMap {
			//节点已在PeerStore中就跳过
			if isPeerStored(peerIDStr) {
				continue
			}

			unstoredPeerNum += 1
			//开协程挨个和未知节点握手
			go func(peerIDStr string, addrStrs []string) {
				peerID, err := conversion.StrToPeerID(peerIDStr)
				if err != nil {
					logger.Error(map[string]interface{}{"[p2p] [handle bootstrap stream] core.StrToPeerID()": err})
					return
				}

				//将未知节点的所有multiaddr加入PeerStore
				for _, addrStr := range addrStrs {
					addr, err := conversion.StrToMultiaddr(addrStr)
					if err != nil {
						logger.Error(map[string]interface{}{"[p2p] [handle bootstrap stream] core.StrToPeerID()": err})
						continue
					}
					AddAddr(peerID, addr)
				}

				//准备握手
				peerDto := dto.PeerDto{
					BaseDto: dto.BaseDto{
						Code: dto.CODE_PING,
					},
					SenderPeerID:     GetSelfID().String(),
					SenderMultiaddrs: GetSelfMultiaddrStrs(),
					SenderPubKey:     ed25519.GetPeerPubKeyStr(),
				}
				peerDto.HashAndSign()
				jsonStr, err := json.Marshal(peerDto)
				if err != nil {
					logger.Error(map[string]interface{}{"[p2p] [handle bootstrap stream] mrashal peerDto": err})
					return
				}

				OpenStream(PingProtocol{}, peerID, string(jsonStr), nil)
			}(peerIDStr, addrStrs)
		}

		logger.Printf("Get %d unstored peer by bootstrap\n", unstoredPeerNum)
		logger.Println("Start shaking hands with unstored peers in other goroutines")

	}()
}

func isPeerStored(peerIDStr string) bool {
	peers := GetPeerIDs()
	for _, storedPeerID := range peers {
		if storedPeerID.String() == peerIDStr {
			return true
		}
	}

	return false
}

func (protocol BootstrapProtocol) handleStream(s network.Stream) {
	logger.Println("Got a new stream " + protocol.protocolName())
	logger.Info(map[string]interface{}{fmt.Sprintf("[p2p] [Handle Stream %s]", protocol.protocolName()): "Got a new stream "})

	// Create a buffer stream for non blocking read and write.
	rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))

	//写协程
	go func() {

		multiPeerDto := dto.MultiPeerDto{
			AddrMap: GetAddrMap(),
		}

		jsonBytes, err2 := json.Marshal(multiPeerDto)
		if err2 != nil {
			logger.Error(map[string]interface{}{fmt.Sprintf("[p2p] [Handle stream %s] PeerDto marshal", protocol.protocolName()): err2.Error()})
			return
		}

		rw.WriteString(fmt.Sprintf("%s\n", string(jsonBytes)))
		rw.Flush()

		//等待网络流被对方节点关闭
		_, err := rw.ReadString('\n')
		if err != nil {
			//TODO confirm: what exactly will happen if the remote side close the stream?
			//if err == io.EOF {}
			logger.Printf("Stream %s is closed by the remote side\n", protocol.protocolName())
			logger.Info(map[string]interface{}{fmt.Sprintf("[p2p] [Handle Stream %s]", protocol.protocolName()): "Stream closed by the remote side"})
			return
		}
	}()
}
